// =============================================================================
//  Spangles Analyzer
//
//  Copyright(c) 2008
//  See LICENSE.txt for licensing information.
// =============================================================================

package com.google.spangles.view.renderers;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.LinkedList;
import java.util.List;

/**
 * (Description)
 * (Any invariants [conditions that are always true] )
 * (Usage instructions, and/or examples)
 * (TODOs, ASSUMPTIONs, etc.)
 * (Known Bugs)
 * (Concurrency strategy - not thread safe or how it's thread safe)
 * 
 * @author Chris
 */
public class GraphicsContext
{
   public static final int WHITE_DOWN = 0;
   public static final int WHITE_UP = 1;
   public static final int BLACK_DOWN = 2;
   public static final int BLACK_UP = 3;
   public static final int GRAY_DOWN = 4;
   public static final int GRAY_UP = 5;
   public static final int EMPTY_DOWN = 6;
   public static final int EMPTY_UP = 7;   
   
   List<BufferedImage> images = new LinkedList<BufferedImage>();
   
   int xSize;
   
   int ySize;
   
   int bevel;
   
   int contrast;
   
   int[] whitePen;
   
   int[] blackPen;
   
   int[] grayPen;
   
   int[] boardPen;
   
   BufferedImage canvas;
   
   Graphics2D graphics;
   
   int xOffset;
   
   int yOffset;
   
   boolean top;
   
   boolean bottom;
   
   boolean left;
   
   boolean right;
   
   int slack;
   
   String fontName;
   
   int fontSize;
   
   Color fontColor;
   
   int[] gridPen;

   /**
    * @param go
    */
   public GraphicsContext(GraphicsOptions go)
   {
      this.xSize = go.getSize();
      this.bevel = go.getBevel();
      this.contrast = go.getContrast();
      
      this.whitePen = createPen(go.getWhite());
      this.blackPen = createPen(go.getBlack());
      this.grayPen = createPen(go.getGray());
      this.boardPen = createPen(go.getBoard());
      
      this.top =go.isTop();
      this.bottom =go.isBottom();
      this.left =go.isLeft();
      this.right =go.isRight();
      
      this.slack = go.getSlack();
      
      this.fontName = go.getFontName();
      this.fontSize = go.getFontSize();
      
      this.fontColor = createColor(go.getFontColor());

      this.gridPen = createPen(go.getBoardGrid());
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param white
    * @return
    */
   private int[] createPen(String color)
   {
      int[] pen = new int[4];
      
      pen[0] = Integer.valueOf("0" + color.substring(0,2), 16);
      pen[1] = Integer.valueOf("0" + color.substring(2,4), 16);
      pen[2] = Integer.valueOf("0" + color.substring(4,6), 16);
      
      pen[3]=0x00FF;
      return pen;
   }

   private Color createColor(String color)
   {      
      int r = Integer.valueOf("0" + color.substring(0,2), 16);
      int g = Integer.valueOf("0" + color.substring(2,4), 16);
      int b = Integer.valueOf("0" + color.substring(4,6), 16);
      
      return new Color(r,g,b);
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param bi
    */
   public void addImage(BufferedImage bi)
   {
      images.add(bi);
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public int getXSize()
   {
      return xSize;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param ysize
    */
   public void setYSize(int ySize)
   {
      this.ySize = ySize;      
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public int getBevel()
   {
      return bevel;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public int getContrast()
   {
      return contrast;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public int[] getGrayPen()
   {
      return grayPen;
   }

   /**
    * @return Returns the whitePen.
    */
   public int[] getWhitePen()
   {
      return whitePen;
   }

   /**
    * @return Returns the blackPen.
    */
   public int[] getBlackPen()
   {
      return blackPen;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public BufferedImage getCanvas()
   {
      return canvas;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param x
    * @param y
    * @param intValue
    */
   public void render(int x, int y, int tile)
   {
      int xc = xOffset + x * xSize;
      int yc = yOffset + y * ySize;
      
      BufferedImage image = images.get(tile);
      
      graphics.drawImage(image, new AffineTransform(1f,0f,0f,1f,xc,yc), null);
      
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param xs
    * @param ys
    */
   public void prepareCanvas(int xs, int ys)
   {
      xOffset = slack;
      yOffset = slack;
      
      int canvasX = slack*2;
      int canvasY = slack*2;
      
      if(top)
      {
         yOffset += ySize;
         canvasY += ySize;
      }
      if(bottom)
      {
         canvasY += ySize;
      }
      if(left)
      {
         xOffset += xSize;
         canvasX += xSize;
      }
      if(right)
      {
         canvasX += xSize;
      }
      
      canvasX += ((xs+1)*xSize+1);
      canvasY += (ys*ySize+1);
      
      canvas = new BufferedImage(canvasX, canvasY, BufferedImage.TYPE_4BYTE_ABGR);
      
      WritableRaster r = canvas.getRaster();
      
      for(int y=0;y<canvasY;y++)
      {
         for(int x=0; x<canvasX; x++)
         {
            r.setPixel(x, y, boardPen);
         }
      }
      
      graphics = canvas.createGraphics();
      
      // draw the text labels on there now
      Font font = new Font(fontName, 0, fontSize);
      graphics.setFont(font);
      graphics.setColor(fontColor);
      FontMetrics fm = graphics.getFontMetrics();
      
      int ascent = fm.getAscent();
      
      for(int x=0 ; x<xs ; x++)
      {
         Character c = new Character((char)('A'+x));
         String asString = c.toString();
         int width = fm.stringWidth(asString);
       
         int x1 = xOffset + x*xSize;
         int x2 = x1 + 2*xSize;
         
         int xm = (x1+x2)/2;
         for(int y=0; y<canvasY; y++)
         {
            r.setPixel(xm, y, gridPen);
         }

         if (top)
         {
            int y1 = 0;
            int y2 = ySize;
            centerText(graphics,ascent,x1,y1,x2,y2,asString,width);
         }
         
         if (bottom)
         {
            int y1 = canvasY - ySize;
            int y2 = canvasY;
            centerText(graphics,ascent,x1,y1,x2,y2,asString,width);
         }         
      }

      for(int y=0 ; y<ys ; y++)
      {
         String asString = Integer.toString(y+1);
         int width = fm.stringWidth(asString);
       
         int y1 = yOffset + y*ySize;
         int y2 = y1 + ySize;
         
         int ym = (y1+y2)/2;
         for(int x=0; x<canvasX; x++)
         {
            r.setPixel(x, ym, gridPen);
         }
         
         if (left)
         {
            int x1 = 0;
            int x2 = xSize;
            centerText(graphics,ascent,x1,y1,x2,y2,asString,width);
         }
         
         if (right)
         {
            int x1 = canvasX - xSize;
            int x2 = canvasX;
            centerText(graphics,ascent,x1,y1,x2,y2,asString,width);
         }
      }
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param graphics2
    * @param ascent
    * @param x1
    * @param y1
    * @param x2
    * @param y2
    * @param asString
    * @param width
    */
   private void centerText(Graphics2D graphics2, int ascent, int x1, int y1,
                           int x2, int y2, String asString, int width)
   {
      int baseLift = ((y2-y1)-ascent)/2;
      int horizontalIndent = ((x2-x1)-width)/2;
      
      graphics2.drawString(asString, x1+horizontalIndent, y2-baseLift);
   }

}
